package com.cskaoyan.mall.order.controller;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cskaoyan.mall.cart.api.dto.CartInfoDTO;
import com.cskaoyan.mall.common.constant.ResultCodeEnum;
import com.cskaoyan.mall.common.execption.BusinessException;
import com.cskaoyan.mall.common.result.Result;
import com.cskaoyan.mall.common.util.AuthContext;
import com.cskaoyan.mall.common.util.DateUtil;
import com.cskaoyan.mall.mq.constant.MqTopicConst;
import com.cskaoyan.mall.mq.producer.BaseProducer;
import com.cskaoyan.mall.order.client.CartApiClient;
import com.cskaoyan.mall.order.client.ProductApiClient;
import com.cskaoyan.mall.order.client.UserApiClient;
import com.cskaoyan.mall.order.client.WareApiClient;
import com.cskaoyan.mall.order.constant.OrderStatus;
import com.cskaoyan.mall.order.controller.chain.*;
import com.cskaoyan.mall.order.converter.CartInfoConverter;
import com.cskaoyan.mall.order.converter.OrderDetailConverter;
import com.cskaoyan.mall.order.converter.OrderInfoConverter;
import com.cskaoyan.mall.order.dto.OrderDetailDTO;
import com.cskaoyan.mall.order.dto.OrderInfoDTO;
import com.cskaoyan.mall.order.dto.OrderTradeDTO;
import com.cskaoyan.mall.order.model.OrderInfo;
import com.cskaoyan.mall.order.query.OrderDetailParam;
import com.cskaoyan.mall.order.query.OrderInfoParam;
import com.cskaoyan.mall.order.service.OrderService;
import com.cskaoyan.mall.user.dto.UserAddressDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author Pudding
 * @Date 2024/7/17 9:59
 */
@RestController
public class OrderController {

    @Autowired
    UserApiClient userApiClient;
    @Autowired
    CartApiClient cartApiClient;
    @Autowired
    CartInfoConverter cartInfoConverter;
    @Autowired
    WareApiClient wareApiClient;
    @Autowired
    ProductApiClient productApiClient;
    @Autowired
    OrderDetailConverter orderDetailConverter;
    @Autowired
    BaseProducer baseProducer;
    @Autowired
    OrderInfoConverter orderInfoConverter;
    @Autowired
    OrderService orderService;
    @Autowired
    ValidationHandler validationHandler;
    @Autowired
    DeleteCartHandler deleteCartHandler;
    @Autowired
    SaveOrderHandler saveOrderHandler;
    @Autowired
    SendMessageHandler sendMessageHandler;


    /**
     * 1. 去结算，获取结算页面的内容
     *
     * @param request
     * @return
     */
    @GetMapping("/order/auth/trade")
    public Result<OrderTradeDTO> trade(HttpServletRequest request) {

        //构建OrderTradeDTO对象，用来存储订单信息
        OrderTradeDTO orderTradeDTO = new OrderTradeDTO();

        //1. 获取用户id
        String userId = AuthContext.getUserId(request);

        //2. 获取用户的收获地址列表（需要通过openFeign远程调用用户服务对外暴露的接口）
        List<UserAddressDTO> userAddressListByUserId = userApiClient.findUserAddressListByUserId(userId);
        orderTradeDTO.setUserAddressList(userAddressListByUserId);

        //3. 获取商品列表（需要通过openFeign远程调用购物车服务对外暴露的接口）
        //3.1 获取购物车中被选中的商品列表
        List<CartInfoDTO> cartCheckedList = cartApiClient.getCartCheckedList(userId);

        //3.2 将商品列表转换为订单详情列表
        List<OrderDetailDTO> detailArrayList = cartInfoConverter.convertCartInfoDTOToOrderDetailDTOList(cartCheckedList);
        orderTradeDTO.setDetailArrayList(detailArrayList);

        //4. 计算总数量和总金额
        //4.1 通过上述商品列表计算总数量
        Integer totalNum = 0;
        for (OrderDetailDTO orderDetailDTO : detailArrayList) {
            if (orderDetailDTO != null) {
                totalNum = totalNum + orderDetailDTO.getSkuNum();
            }
        }
        orderTradeDTO.setTotalNum(totalNum);

        //4.2 通过上述商品列表计算总金额
        BigDecimal bigDecimal = new BigDecimal(0);
        for (OrderDetailDTO orderDetailDTO : detailArrayList) {
            bigDecimal = bigDecimal.add(orderDetailDTO.getOrderPrice().multiply(new BigDecimal(orderDetailDTO.getSkuNum())));
        }
        orderTradeDTO.setTotalAmount(bigDecimal);

        //5. 返回
        return Result.ok(orderTradeDTO);
    }


    /**
     * 2. 提交订单：发起下单请求的责任链处理器
     * 1. 校验参数（校验库存是否足够 | 校验价格是否变动）
     * 2. 保存订单和订单明细到数据库
     * 3. 删除购物车中被选中的商品
     * 4. 发送一个订单超时自动取消的消息
     *
     * @param orderInfoParam
     * @param request
     * @return
     */
    /*@PostMapping("/order/auth/submitOrder")
    public Result submitOrder(@RequestBody OrderInfoParam orderInfoParam, HttpServletRequest request) {
        //0. 获取到用户Id
        String userId = AuthContext.getUserId(request);

        //构建一个责任链的处理上下文对象
        HandlerContext handlerContext = HandlerContext.builder()
                .userId(userId)
                .orderInfoParam(orderInfoParam)
                .build();

        //构建一个责任链
        validationHandler.setNextHandler(saveOrderHandler);
        saveOrderHandler.setNextHandler(deleteCartHandler);
        deleteCartHandler.setNextHandler(sendMessageHandler);

        //开始执行业务逻辑
        try {
            validationHandler.handle(handlerContext);
        } catch (BusinessException e) {
            return Result.fail(e.getMessage());
        }

        return Result.ok(handlerContext.getOrderId());
    }*/
    @PostMapping("/order/auth/submitOrder")
    public Result submitOrder(@RequestBody OrderInfoParam orderInfoParam, HttpServletRequest request) {

        //0. 获取到用户Id
        String userId = AuthContext.getUserId(request);

        //1. 校验
        //1.1 校验商品的库存是否足够（在这个商城中，库存服务是单独开发的）
        //1.1.1 遍历订单中商品详细信息获取商品id、商品数量
        for (OrderDetailParam orderDetailParam : orderInfoParam.getOrderDetailList()) {
            Long skuId = orderDetailParam.getSkuId();      //商品id
            Integer skuNum = orderDetailParam.getSkuNum(); //商品数量
            String skuName = orderDetailParam.getSkuName();//商品的名称

            //1.1.2 远程调用仓储服务获取库存，判断该商品是否有足够库存
            Result result = wareApiClient.hasStock(skuId, skuNum);

            if (!result.getCode().equals(ResultCodeEnum.SUCCESS.getCode())) {
                return Result.fail().message(skuName + " 没有库存！");
            }
        }
        //1.2 校验商品价格是否产生变动
        //1.2.1 遍历订单中商品详细信息获取购物车中的商品价格、商品id
        for (OrderDetailParam orderDetailParam : orderInfoParam.getOrderDetailList()) {
            BigDecimal orderPrice = orderDetailParam.getOrderPrice();  //商品价格
            Long skuId = orderDetailParam.getSkuId();                  //商品id
            String skuName = orderDetailParam.getSkuName();            //商品的名称

            //1.2.2 远程调用商品服务获取商品实时价格（对应的服务需要对外暴露接口）
            BigDecimal currentPrice = productApiClient.getSkuPrice(skuId);

            //1.2.3 判断价格是否变动
            //如果不一致，就更新购物车中商品的价格（远程调用购物车中更新用户购物车中商品价格 + 购物车服务中再调用商品服务接口获取最新价格）
            if (!orderPrice.equals(currentPrice)) {

                cartApiClient.refreshCartPrice(userId, skuId);
                //返回价格发生变动，让用户重新提交订单
                return Result.fail().message(skuName + " 的价格发生变动！");
            }
        }


        //2. 保存订单以及订单详情
        //2.1 把orderInfoParam转换为OrderInfo
        OrderInfo orderInfo = orderInfoConverter.convertOrderInfoParam(orderInfoParam);

        //2.2 调用orderService保存订单以及订单详情
        //2.3.1 需要额外设定订单中的一些变量
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());  //order_status
        orderInfo.setUserId(Long.parseLong(userId));          //user_id
        //2.3.2 计算总价格
        orderInfo.sumTotalAmount();                           //调用该方法，可以给总金额赋值 total_amount + origin_total_amount
        //2.3.3 外部交易流水号，给外部系统使用，只需要唯一即可。 UUID | 雪花算法生成的id
        Snowflake snowflake = IdUtil.createSnowflake(1, 1);          // 参数：workId, dataCenterId
        orderInfo.setOutTradeNo(String.valueOf(snowflake.nextId()));                     //out_trade_no：订单交易编号（第三方支付用) 外部交易流水号，给外部系统使用
        orderInfo.setTradeBody(orderInfoParam.getOrderDetailList().get(0).getSkuName()); //trade_body：使用商品名称
        //2.3.4 订单自动超时取消的时间，这个时间需要和第4步中的订单超时自动取消的延迟级别设置的时间 统一起来
        orderInfo.setExpireTime(DateUtil.datePlusMinutes(new Date(), 2));        //expire_time
        //2.4 保存订单和订单明细
        Long orderId = orderService.saveOrderInfo(orderInfo);


        //3. 删除购物车中已经下单的商品
        //3.1 遍历订单中商品详细信息，将List<OrderDetailParam>转换为 List<商品id>
        List<Long> skuIds = orderInfoParam.getOrderDetailList().stream()
                .map(OrderDetailParam::getSkuId)
                .collect(Collectors.toList());
        //3.2 调用购物车中对外暴露的接口删除购物车中已经下单的商品
        cartApiClient.removeCartProductsInOrder(userId, skuIds);


        //4. 发送订单超时自动取消的消息
        //  1  2  3   4   5  6  7  8  9 10 11 12 13  14  15 16  17  18
        // 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h  2h
        baseProducer.sendDelayMessage(MqTopicConst.DELAY_ORDER_TOPIC, orderId,6);


        //5. 返回
        return Result.ok(orderId);
    }


    /**
     * 3. 分页查询我的订单
     * @param page
     * @param limit
     * @param request
     * @return
     */
    @GetMapping("/order/auth/{page}/{limit}")
    public Result<IPage<OrderInfoDTO>> index(@PathVariable Long page, @PathVariable Long limit, HttpServletRequest request) {

        //1. 获取用户id
        String userId = AuthContext.getUserId(request);

        //2. 分页查询这个用户的订单
        //分页参数已知、不仅仅要查询订单，还要查询订单明细
        //2.1 构建分页参数对象
        Page<OrderInfo> orderInfoPage = new Page<>(page, limit);
        IPage<OrderInfoDTO> orderInfoDTOIPage = orderService.getPage(orderInfoPage, userId);

        //思考：
        // 1. 哪些订单状态不应该显示在列表中呢?  比如说已拆分的订单不要不要显示在订单列表中呢？
        // 2. 需要给OrderInfoDTO中的 orderStatusName 这个字段赋值
        orderInfoDTOIPage.getRecords().stream()
                .filter(orderInfoDTO -> !orderInfoDTO.getOrderStatus().equals("SPLIT") && !orderInfoDTO.getOrderStatus().equals("STOCK_EXCEPTION"))
                .forEach(orderInfoDTO -> orderInfoDTO.setOrderStatusName(OrderStatus.getStatusDescByStatus(orderInfoDTO.getOrderStatus())));

        return Result.ok(orderInfoDTOIPage);
    }
}
